/*
* Copyright (C) 2005-2012 BetaCONCEPT Limited
*
* This file is part of Astroboa.
*
* Astroboa is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Astroboa is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Astroboa. If not, see <http://www.gnu.org/licenses/>.
*/
package org.betaconceptframework.astroboa.engine.jcr.dao;
import java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.jcr.Repository;
import javax.jcr.SimpleCredentials;
import javax.security.auth.Subject;
import javax.security.auth.login.AccountExpiredException;
import javax.security.auth.login.AccountLockedException;
import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.CredentialExpiredException;
import javax.security.auth.login.CredentialNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.betaconceptframework.astroboa.api.model.CmsRepository;
import org.betaconceptframework.astroboa.api.model.RepositoryUser;
import org.betaconceptframework.astroboa.api.model.exception.CmsException;
import org.betaconceptframework.astroboa.api.security.AstroboaCredentials;
import org.betaconceptframework.astroboa.api.security.AstroboaPrincipalName;
import org.betaconceptframework.astroboa.api.security.CmsRole;
import org.betaconceptframework.astroboa.api.security.IdentityPrincipal;
import org.betaconceptframework.astroboa.api.security.RepositoryUserIdPrincipal;
import org.betaconceptframework.astroboa.api.security.exception.CmsInvalidPasswordException;
import org.betaconceptframework.astroboa.api.security.exception.CmsLoginAccountExpiredException;
import org.betaconceptframework.astroboa.api.security.exception.CmsLoginAccountLockedException;
import org.betaconceptframework.astroboa.api.security.exception.CmsLoginInvalidCredentialsException;
import org.betaconceptframework.astroboa.api.security.exception.CmsLoginInvalidPermanentKeyException;
import org.betaconceptframework.astroboa.api.security.exception.CmsLoginInvalidUsernameException;
import org.betaconceptframework.astroboa.api.security.exception.CmsLoginPasswordExpiredException;
import org.betaconceptframework.astroboa.api.security.exception.CmsUnauthorizedRepositoryUseException;
import org.betaconceptframework.astroboa.api.security.management.IdentityStore;
import org.betaconceptframework.astroboa.api.security.management.IdentityStoreContextHolder;
import org.betaconceptframework.astroboa.api.service.RepositoryUserService;
import org.betaconceptframework.astroboa.cache.region.DefinitionCacheRegion;
import org.betaconceptframework.astroboa.configuration.LocalizationType.Label;
import org.betaconceptframework.astroboa.configuration.RepositoryRegistry;
import org.betaconceptframework.astroboa.configuration.RepositoryType;
import org.betaconceptframework.astroboa.configuration.SecurityType.PermanentUserKeyList.PermanentUserKey;
import org.betaconceptframework.astroboa.context.AstroboaClientContext;
import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder;
import org.betaconceptframework.astroboa.context.CmsRepositoryImpl;
import org.betaconceptframework.astroboa.context.RepositoryContext;
import org.betaconceptframework.astroboa.context.SecurityContext;
import org.betaconceptframework.astroboa.engine.definition.ContentDefinitionConfiguration;
import org.betaconceptframework.astroboa.engine.jcr.initialization.CmsRepositoryInitializationManager;
import org.betaconceptframework.astroboa.engine.jcr.util.JackrabbitDependentUtils;
import org.betaconceptframework.astroboa.engine.service.security.AstroboaLogin;
import org.betaconceptframework.astroboa.model.lazy.LazyLoader;
import org.betaconceptframework.astroboa.security.CmsGroup;
import org.betaconceptframework.astroboa.security.CmsPrincipal;
import org.betaconceptframework.astroboa.security.CmsRoleAffiliationFactory;
import org.betaconceptframework.astroboa.security.CredentialsCallbackHandler;
import org.betaconceptframework.astroboa.util.CmsConstants;
import org.betaconceptframework.astroboa.util.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.io.FileSystemResource;
import org.springframework.extensions.jcr.JcrSessionFactory;
import org.springframework.extensions.jcr.SessionFactory;
import org.springframework.extensions.jcr.jackrabbit.RepositoryFactoryBean;
/**
* @author Gregory Chomatas (gchomatas@betaconcept.com)
* @author Savvas Triantafyllou (striantafyllou@betaconcept.com)
*
*/
public class RepositoryDao implements ApplicationListener{
@Autowired
private SimpleCredentials betaConceptCredentials;
@Autowired
private CmsRepositoryInitializationManager cmsRepositoryInitializationManager;
@Autowired
private ContentDefinitionConfiguration contentDefinitionConfiguration;
@Autowired
//Used mainly when initializing IdentityStore repository
private LazyLoader lazyLoader;
@Autowired
//Bound to service so that a new Transaction is created
//as this is used when RepositoryUserPrincipal must be added to subject
private RepositoryUserService repositoryUserService;
@Autowired
private DefinitionCacheRegion definitionCacheRegion;
@Autowired
private ConsistencyCheckerDao consistencyCheckerDao;
@Autowired
private IdentityStore identityStore;
private final Logger logger = LoggerFactory.getLogger(RepositoryDao.class);
private Map<String, Repository> jcrRepositories = new HashMap<String, Repository>();
private Map<String, CmsRepository> repositoryInfos = new HashMap<String, CmsRepository>();
private Map<String, SessionFactory> jcrSessionFactoriesPerRepository = new HashMap<String, SessionFactory>();
private void loadRepositoryFromConfiguration(String repositoryId) {
if (!RepositoryRegistry.INSTANCE.isRepositoryRegistered(repositoryId)){
logger.warn("Found no configuration for repository "+repositoryId);
return;
}
try{
RepositoryType repositoryConfiguration = RepositoryRegistry.INSTANCE.getRepositoryConfiguration(repositoryId);
//Create a CmsRepository instance which holds all configuration parameters
CmsRepository cmsRepository = loadConfigurationParameters(repositoryId, repositoryConfiguration);
//Create JcrSessionFactory
SessionFactory jcrSessionFactory = createJcrSessionFactory(repositoryId, repositoryConfiguration.getRepositoryHomeDirectory());
jcrSessionFactoriesPerRepository.put(repositoryId, jcrSessionFactory);
if (fullReloadRepository(repositoryId, repositoryConfiguration)){
if (! repositoryInfos.containsKey(repositoryId)){
repositoryInfos.put(repositoryId, cmsRepository);
}
//Initialize repository and load definition to cache
SecurityContext securityContext = new SecurityContext(repositoryId, null, 30, null);
RepositoryContext repositoryContext = new RepositoryContext(cmsRepository, securityContext);
AstroboaClientContextHolder.registerClientContext(new AstroboaClientContext(repositoryContext, new LazyLoader(null, null, null, null)), true);
try{
cmsRepositoryInitializationManager.initialize(cmsRepository);
contentDefinitionConfiguration.loadDefinitionToCache();
if (repositoryConfiguration.isCheckConsistency() || RepositoryRegistry.INSTANCE.isConsistencyCheckEnabled()){
consistencyCheckerDao.performReferentialIntegrityCheck();
}
}
catch(CmsException e){
repositoryInfos.remove(repositoryId);
jcrSessionFactoriesPerRepository.remove(repositoryId);
AstroboaClientContextHolder.clearContext();
throw e;
}
AstroboaClientContextHolder.clearContext();
// Create unmanaged datastore if not there
try{
File repositoryHomeDir = new File(cmsRepository.getRepositoryHomeDirectory());
if (repositoryHomeDir.exists()){
File unmanagedDataStoreDirectory = new File(repositoryHomeDir, CmsConstants.UNMANAGED_DATASTORE_DIR_NAME);
if (!unmanagedDataStoreDirectory.exists()){
if (!unmanagedDataStoreDirectory.mkdir()){
throw new Exception("Could not create UnmanagedDataStore directory. File.mkdir() returned false");
}
logger.debug("Created UnmanagedDataStore directory for repository {} in path {}",cmsRepository.getId(), unmanagedDataStoreDirectory.getAbsolutePath());
}
}
}
catch(Error e){
logger.warn("Could not create UnmanagedDataStore directory", e);
}
}
}
catch(Exception e){
logger.error("",e);
return;
}
}
private CmsRepository loadConfigurationParameters(String repositoryId,
RepositoryType repositoryConfiguration) {
String serverAliasURL = repositoryConfiguration.getServerAliasURL();
String restfulApiBasePath = repositoryConfiguration.getRestfulApiBasePath();
String repositoryServerURL = StringUtils.isBlank(serverAliasURL) ? RepositoryRegistry.INSTANCE.getDefaultServerURL() : serverAliasURL;
String externalIdentityStoreJndiName = repositoryConfiguration.getExternalIdentityStoreJndiName();
String repositoryIdentityStoreId = null;
if (StringUtils.isBlank(externalIdentityStoreJndiName)){
repositoryIdentityStoreId = (repositoryConfiguration.getIdentityStoreRepositoryId() == null )?
RepositoryRegistry.INSTANCE.getDefaultIdentityStoreId():
repositoryConfiguration.getIdentityStoreRepositoryId();
if (StringUtils.isBlank(repositoryIdentityStoreId)){
//Define the repository to be identity store for it self
repositoryIdentityStoreId = repositoryId;
}
}
//Localized Labels
HashMap<String, String> localizedLabels = new HashMap<String, String>();
if (repositoryConfiguration.getLocalization() != null){
List<Label> localizedLabelList = repositoryConfiguration.getLocalization().getLabel();
for (Label localizedLabel : localizedLabelList){
localizedLabels.put(localizedLabel.getLang(), localizedLabel.getValue());
}
}
CmsRepository cmsRepository = new CmsRepositoryImpl(repositoryId, localizedLabels,
repositoryConfiguration.getRepositoryHomeDirectory(),
repositoryServerURL,
restfulApiBasePath,
repositoryIdentityStoreId,
externalIdentityStoreJndiName,
repositoryConfiguration.getSecurity().getSecretUserKeyList().getAdministratorSecretKey().getUserid());
return cmsRepository;
}
private SessionFactory createJcrSessionFactory(String repositoryId, String repositoryHomeDirectory) throws Exception {
if (! jcrRepositories.containsKey(repositoryId)){
//create repository first
RepositoryFactoryBean repositoryFactory = new RepositoryFactoryBean();
repositoryFactory.setHomeDir(new FileSystemResource(repositoryHomeDirectory));
repositoryFactory.setConfiguration(new FileSystemResource(repositoryHomeDirectory+File.separator+"repository.xml"));
repositoryFactory.afterPropertiesSet();
// FileSystemResource configuration = new FileSystemResource(repositoryHomeDirectory+File.separator+"repository.xml");
// FileSystemResource homeDir = new FileSystemResource(repositoryHomeDirectory);
//
// RepositoryConfig repositoryConfig = RepositoryConfig.create(new InputSource(configuration.getInputStream()), homeDir.getFile().getAbsolutePath());
//
// Repository jcrRepository = RepositoryImpl.create(repositoryConfig);
Repository jcrRepository = repositoryFactory.getObject();
if (jcrRepository == null){
throw new CmsException("Unable to initialize repository "+ repositoryId + " located in "+ repositoryHomeDirectory);
}
jcrRepositories.put(repositoryId, jcrRepository);
}
//Create JcrSessionFactory For default workspace
JcrSessionFactory jcrSessionFactory = new JcrSessionFactory();
jcrSessionFactory.setCredentials(betaConceptCredentials);
jcrSessionFactory.setRepository((Repository)jcrRepositories.get(repositoryId));
//Call this method to force further initialization process
jcrSessionFactory.afterPropertiesSet();
return jcrSessionFactory;
}
public List<CmsRepository> getAvailableCmsRepositories() {
deployRepositoriesFoundInTheRepositoryRegistry();
return new ArrayList<CmsRepository>(repositoryInfos.values());
}
private void deployRepositoriesFoundInTheRepositoryRegistry() {
if (RepositoryRegistry.INSTANCE.configurationHasChanged() || repositoryInfos.isEmpty()){
RepositoryRegistry.INSTANCE.loadRepositoryConfigurations();
/*
* Key is the the repository home directory for the JCR Repository, value is the repository id
*/
Map<String,String> jcrRepositoryHomeDirectoryPerRepository = new HashMap<String, String>();
Set<String> repositoryIdsToBeUndeployed = new HashSet<String>();
for (Map.Entry<String, RepositoryType> repositoryConfigurationEntry : RepositoryRegistry.INSTANCE.getConfigurationsPerRepositoryId().entrySet()){
String repositoryId = repositoryConfigurationEntry.getKey();
RepositoryType repositoryConfiguration = repositoryConfigurationEntry.getValue();
String currentJcrRepositoryHomeDirectory = repositoryConfiguration.getRepositoryHomeDirectory();
boolean initializeRepository = true;
//Check for duplicate repository home directory
if (jcrRepositoryHomeDirectoryPerRepository.containsKey(currentJcrRepositoryHomeDirectory)){
logger.warn("Repository Home Directory '{}' already defined for repository '{}'. Repository {} will not be loaded.",
new Object[]{currentJcrRepositoryHomeDirectory, jcrRepositoryHomeDirectoryPerRepository.get(currentJcrRepositoryHomeDirectory), repositoryId});
repositoryIdsToBeUndeployed.add(repositoryId);
initializeRepository = false;
}
if (initializeRepository){
loadRepositoryFromConfiguration(repositoryId);
}
}
//Mark any repository which does not exist in the configuration as to be undeployed
//This step is meaningful only when the configuration file is updated
for (String deployedRepositoryId : repositoryInfos.keySet()){
if (!RepositoryRegistry.INSTANCE.isRepositoryRegistered(deployedRepositoryId)){
logger.warn("Repository {} does not belong to the configuration and therefore will be undeployed", deployedRepositoryId);
repositoryIdsToBeUndeployed.add(deployedRepositoryId);
}
}
//Undeploy any repository whose configuration is invalid
for (String repositoyIdToBeUndeployed : repositoryIdsToBeUndeployed){
repositoryInfos.remove(repositoyIdToBeUndeployed);
jcrSessionFactoriesPerRepository.remove(repositoyIdToBeUndeployed);
jcrRepositories.remove(repositoyIdToBeUndeployed);
//Clear caches
definitionCacheRegion.clearCacheForRepository(repositoyIdToBeUndeployed);
logger.warn("Successfully undeployed repository {}", repositoryIdsToBeUndeployed);
}
//Detect any obvious cycles between repository and identity store repositoryInfos
detectCycleBetweenRepositoryAndIdentityStoreRepositories();
//Second pass to initialize any repository which is an identity store for another repository
for (CmsRepository cmsRepository : repositoryInfos.values()){
initializeIdentityStoreForRepository(cmsRepository);
}
}
}
private boolean fullReloadRepository(String repositoryId, RepositoryType repositoryConfiguration) {
//Reload repository if repository has not been loaded
if (! repositoryInfos.containsKey(repositoryId)){
return true;
}
//Check if cache manager settings have changed
Repository repository = jcrRepositories.get(repositoryId);
return JackrabbitDependentUtils.cacheManagerSettingsHaveChanged(repositoryConfiguration, repository);
}
private void initializeIdentityStoreForRepository(
CmsRepository cmsRepository) {
if (StringUtils.isBlank(cmsRepository.getExternalIdentityStoreJNDIName())){
String identityStoreRepositoryId = cmsRepository.getIdentityStoreRepositoryId();
if (StringUtils.isBlank(identityStoreRepositoryId)){
throw new CmsException("No external IdentityStore JNDI has been provided nor an identity store repository id for repository "+ cmsRepository.getId());
}
if (!repositoryInfos.containsKey(identityStoreRepositoryId)){
throw new CmsException("Found no repository with id "+identityStoreRepositoryId+".Cannot initialize identity store for repository "+ cmsRepository.getId());
}
CmsRepository cmsRepositoryIdentityStore = repositoryInfos.get(identityStoreRepositoryId);
Subject subject = new Subject();
subject.getPrincipals().add(new IdentityPrincipal(IdentityPrincipal.SYSTEM));
Group rolesPrincipal = new CmsGroup(AstroboaPrincipalName.Roles.toString());
for (CmsRole cmsRole : CmsRole.values()){
rolesPrincipal.addMember(new CmsPrincipal(CmsRoleAffiliationFactory.INSTANCE.getCmsRoleAffiliationForRepository(cmsRole, identityStoreRepositoryId)));
}
subject.getPrincipals().add(rolesPrincipal);
SecurityContext securityContext = new SecurityContext(identityStoreRepositoryId,
subject, 30, null);
RepositoryContext repositoryContext = new RepositoryContext(cmsRepositoryIdentityStore, securityContext);
AstroboaClientContextHolder.registerClientContext(new AstroboaClientContext(repositoryContext,
lazyLoader), true);
cmsRepositoryInitializationManager.initializeIdentityStore(cmsRepository.getId(), cmsRepositoryIdentityStore);
AstroboaClientContextHolder.clearContext();
}
}
/*
* A repository may refer to an external Identity Store or to another repository.
*
* At the latter case, the repository that serves as an identity store MUST
* have ITSELF as an identity store.
*
*/
private void detectCycleBetweenRepositoryAndIdentityStoreRepositories() {
List<String> repositoryIdsToBeRemoved = new ArrayList<String>();
for (CmsRepository cmsRepository : repositoryInfos.values()){
if (StringUtils.isNotBlank(cmsRepository.getExternalIdentityStoreJNDIName())){
//We are only interested in repositoryInfos which refer to
//another repository as identity store
continue;
}
else{
if (cmsRepository.getIdentityStoreRepositoryId()==null){
throw new CmsException("No external IdentityStore JNDI has been provided nor an identity store repository id for repository "+ cmsRepository.getId());
}
else{
if (!StringUtils.equals(cmsRepository.getId(), cmsRepository.getIdentityStoreRepositoryId())){
//Repository refers to another repository for identity store.
//Check that this repository refers to itself as identity store
CmsRepository identityStoreRepository = repositoryInfos.get(cmsRepository.getIdentityStoreRepositoryId());
if (StringUtils.isNotBlank(identityStoreRepository.getExternalIdentityStoreJNDIName())){
logger.warn("Repository "+ cmsRepository.getId() + " refers to repository "+
identityStoreRepository.getId() +" to be its identity store, but the latter refers to an external identity store " +
" which is not accepted.Both repositoties will be removed");
repositoryIdsToBeRemoved.add(cmsRepository.getId());
repositoryIdsToBeRemoved.add(identityStoreRepository.getId());
}
else {
if (!StringUtils.equals(identityStoreRepository.getId(), identityStoreRepository.getIdentityStoreRepositoryId())){
logger.warn("Repository "+ cmsRepository.getId() + " refers to repository "+
identityStoreRepository.getId() +" to be its identity store, but the latter refers to another repository for" +
" identity store ("+identityStoreRepository.getIdentityStoreRepositoryId()+") and not its self" +
" which is not accepted.Both repositoties will be removed");
repositoryIdsToBeRemoved.add(cmsRepository.getId());
repositoryIdsToBeRemoved.add(identityStoreRepository.getId());
}
}
}
}
}
}
for (String repositoyToBeRemoved : repositoryIdsToBeRemoved){
repositoryInfos.remove(repositoyToBeRemoved);
jcrSessionFactoriesPerRepository.remove(repositoyToBeRemoved);
jcrRepositories.remove(repositoyToBeRemoved);
}
}
public String login(String repositoryId, AstroboaCredentials credentials) {
return login(repositoryId, credentials, null, null);
}
private boolean isPermanentKeyValidForUser(String repositoryId, String permanentKey, String userid) {
if (StringUtils.isBlank(repositoryId) || StringUtils.isBlank(permanentKey) || StringUtils.isBlank(userid)){
return false;
}
if (!RepositoryRegistry.INSTANCE.isRepositoryRegistered(repositoryId)){
return false;
}
RepositoryType repositoryConfiguration = RepositoryRegistry.INSTANCE.getRepositoryConfiguration(repositoryId);
//If no trusted key have been registered to configuration then all keys are accepted
if (repositoryConfiguration.getSecurity() == null || repositoryConfiguration.getSecurity().getPermanentUserKeyList() == null ||
CollectionUtils.isEmpty(repositoryConfiguration.getSecurity().getPermanentUserKeyList().getPermanentUserKey())){
return false;
}
List<PermanentUserKey> permanentUserKeys = repositoryConfiguration.getSecurity().getPermanentUserKeyList().getPermanentUserKey();
for (PermanentUserKey permanentUserKey : permanentUserKeys){
if (StringUtils.equals(permanentUserKey.getKey(), permanentKey)){
List<String> userIds = Arrays.asList(StringUtils.split(permanentUserKey.getUserid(), ","));
return userIds.contains("*") || userIds.contains(userid);
}
}
return false;
}
private SecurityContext authenticate(AstroboaCredentials credentials, String repositoryId, int currentAuthenticationTokenTimeout, String permanentKey ) {
SecurityContext securityContext = null;
Subject subject = null;
try {
CredentialsCallbackHandler callbackHandler = null;
if (credentials != null){
callbackHandler = new CredentialsCallbackHandler(credentials);
}
IdentityStoreContextHolder.setActiveRepositoryId(repositoryId);
AstroboaLogin astroboaLogin = new AstroboaLogin(callbackHandler, identityStore, this);
subject = astroboaLogin.login();
}
catch(AccountNotFoundException e){
throw new CmsLoginInvalidUsernameException(e);
}
catch(FailedLoginException e){
throw new CmsInvalidPasswordException(e);
}
catch(AccountLockedException e){
throw new CmsLoginAccountLockedException(e);
}
catch(AccountExpiredException e){
throw new CmsLoginAccountExpiredException(e);
}
catch(CredentialNotFoundException e){
throw new CmsInvalidPasswordException(e);
}
catch(CredentialExpiredException e){
throw new CmsLoginPasswordExpiredException(e);
}
catch (LoginException e) {
throw new CmsException(e);
}
catch(CmsException e){
throw e;
}
catch(Throwable t){
throw new CmsException(t);
}
finally{
IdentityStoreContextHolder.clear();
}
authorizeSubject(subject, repositoryId);
try{
String authenticationToken = createAuthenticationToken(subject, repositoryId, permanentKey);
securityContext = new SecurityContext(authenticationToken, subject, currentAuthenticationTokenTimeout,
getAvailableRepositoryIds());
if (logger.isDebugEnabled()){
logger.debug("Successfull authentication: Token {} , Subject {} for Thread {}"
, new Object[]{authenticationToken, subject, Thread.currentThread()});
}
return securityContext;
}
catch (NoSuchAlgorithmException e) {
throw new CmsException(e);
}
}
private List<String> getAvailableRepositoryIds() {
return new ArrayList<String>(RepositoryRegistry.INSTANCE.getConfigurationsPerRepositoryId().keySet());
}
private String createAuthenticationToken(Subject subject, String repositoryId, String permanentKey)
throws NoSuchAlgorithmException {
//create authentication token
StringBuilder stringToDigest = new StringBuilder(repositoryId);
//Token contains the value provided in the UserPrincipal
//provided in Subject. If none is found throw an exception
if (subject == null){
throw new CmsException("No subject was found after login");
}
String identity = retrieveIdentityFromSubject(subject);
stringToDigest.append(identity);
if (StringUtils.isBlank(permanentKey)){
stringToDigest.append(DateUtils.format(Calendar.getInstance()))
.append(UUID.randomUUID());
}
else{
if (StringUtils.isNotBlank(permanentKey) && ! isPermanentKeyValidForUser(repositoryId,permanentKey, identity)){
throw new CmsLoginInvalidPermanentKeyException("Invalid permanent key "+ permanentKey + " for user "+ identity + " in repository "+ repositoryId);
}
stringToDigest.append(permanentKey);
}
//According to documentation in Base64 class
//Encoding is done as defined in RFC 2045 - 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies
//Since this character set contains '+' and '/' chars , these characters must be replaced by '_' and '-' accordingly
//so that this token can be used in URLs if necessary. Since decoding process is not an issue the above chars can be used
//although they are not defined in the 64-character set.
MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
return new String(Base64.encodeBase64(messageDigest.digest(stringToDigest.toString().getBytes()))).replaceAll("\\+", "_").replaceAll("/", "-");
}
private String retrieveIdentityFromSubject(Subject subject) {
if (subject == null || CollectionUtils.isEmpty(subject.getPrincipals(IdentityPrincipal.class))){
throw new CmsException("Could not find identity principal in subject "+subject+" Unable to create authentication token");
}
//Retrieve the first one. Normally it should not have more than one
return subject.getPrincipals(IdentityPrincipal.class).iterator().next().getName();
}
private int retrieveAuthenticationTokenTimeoutForRepository(String repositoryId) {
RepositoryType repositoryConfigurationEntry = RepositoryRegistry.INSTANCE.getRepositoryConfiguration(repositoryId);
if (repositoryConfigurationEntry == null){
throw new CmsException("Could not find configuration entry for repository "+ repositoryId);
}
return repositoryConfigurationEntry.getAuthenticationTokenTimeout() == null ? RepositoryRegistry.INSTANCE.getDefaultAuthenticationTokenTimeout() :
repositoryConfigurationEntry.getAuthenticationTokenTimeout();
}
public SessionFactory getJcrSessionFactoryForAssociatedRepository() {
String associatedRepositoryId = getAssociatedRepositoryId();
if (! jcrSessionFactoriesPerRepository.containsKey(associatedRepositoryId)){
if (!repositoryInfos.containsKey(associatedRepositoryId)){
loadRepositoryFromConfiguration(associatedRepositoryId);
if (! jcrSessionFactoriesPerRepository.containsKey(associatedRepositoryId)){
throw new CmsException("Found no jcr session factory for associated repository "+ associatedRepositoryId);
}
}
else{
throw new CmsException("Found no jcr session factory for associated repository "+ associatedRepositoryId);
}
}
return jcrSessionFactoriesPerRepository.get(associatedRepositoryId);
}
private String getAssociatedRepositoryId() {
String associatedRepositoryId = AstroboaClientContextHolder.getActiveRepositoryId();
if (associatedRepositoryId == null){
throw new CmsException("Found no repository context");
}
return associatedRepositoryId;
}
public boolean isRepositoryAvailable(String repositoryId) {
deployRepositoriesFoundInTheRepositoryRegistry();
return StringUtils.isNotBlank(repositoryId) && repositoryInfos.containsKey(repositoryId);
}
public CmsRepository getCurrentConnectedRepository() {
return AstroboaClientContextHolder.getActiveClientContext() != null ?
( AstroboaClientContextHolder.getActiveClientContext().getRepositoryContext() != null ?
AstroboaClientContextHolder.getActiveClientContext().getRepositoryContext().getCmsRepository() : null) : null;
}
public CmsRepository getCmsRepository(String repositoryId) {
if (StringUtils.isBlank(repositoryId)){
return null;
}
if (!repositoryInfos.containsKey(repositoryId)){
deployRepositoriesFoundInTheRepositoryRegistry();
}
return repositoryInfos.get(repositoryId);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
//Need to initialize all repositoryInfos defined in configuration xml
//after Spring Context has been instantiated
if (event instanceof ContextRefreshedEvent) {
deployRepositoriesFoundInTheRepositoryRegistry();
}
}
private void addRepositoryUserIdPrincipalToSubject(Subject subject,
String identity) {
//Must provide a principal which will hold the RepositoryUserId
//No need to check if user is ANONYMOUS
if (StringUtils.isNotBlank(identity) && ! StringUtils.equals(identity, IdentityPrincipal.ANONYMOUS)){
RepositoryUser repositoryUser = repositoryUserService.getRepositoryUser(identity);
if (repositoryUser != null && repositoryUser.getId() != null){
subject.getPrincipals().add(new RepositoryUserIdPrincipal(repositoryUser.getId()));
}
}
}
/**
* Subject authorization at this level is restricted only to authorize user
* whether she can or cannot login to the specified repository.
* Our default policy is a PERMIT REPOSITORY policy, meaning that an authenticated user
* has access to REPOSITORY available repositoryInfos defined within a Astroboa Server.
*
* In cases where an authenticated user has access to a subset of available repositoryInfos
* then a {@link Group} named after "AuthorizedRepositories" must exist
* among {@link Subject} principals.
*
* If so, only and only if the specified repository exists inside this list,
* the user will be authorized to use Astroboa services for that repository.
*/
private void authorizeSubject(Subject subject, String repositoryId) {
if (subject == null){
throw new CmsException("No subject provided ");
}
//In case authenticated
Set<Principal> principals = subject.getPrincipals();
if (CollectionUtils.isNotEmpty(principals)){
for (Principal principal : principals){
if (principal instanceof Group &&
AstroboaPrincipalName.AuthorizedRepositories.toString().equals(principal.getName()) ){
//Found authorized repositoryInfos
boolean userIsAuthorizedToAccessRepository = false;
for (Enumeration<? extends Principal> authorizedRepositories = ((Group)principal).members(); authorizedRepositories.hasMoreElements();){
Principal authorizedRepository = authorizedRepositories.nextElement();
if (StringUtils.equals(authorizedRepository.getName(), repositoryId)){
userIsAuthorizedToAccessRepository = true;
break;
}
}
if (!userIsAuthorizedToAccessRepository){
throw new CmsUnauthorizedRepositoryUseException(repositoryId);
}
}
}
}
}
public String login(String repositoryId, AstroboaCredentials credentials, String permanentKey, String key)
{
if (StringUtils.isBlank(repositoryId)){
throw new CmsException("Null or empty repository id '"+ repositoryId+"'");
}
//In case configuration has changed, load any changes prior to login
deployRepositoriesFoundInTheRepositoryRegistry();
CmsRepository cmsRepositoryToBeConnected = repositoryInfos.get(repositoryId);
if (cmsRepositoryToBeConnected == null){
throw new CmsException("Repository with id "+ repositoryId+ " was not deployed.");
}
String username = credentials != null ? credentials.getUsername() : null;
if (username == null)
{
throw new CmsLoginInvalidUsernameException("No username provided");
}
if (StringUtils.isNotBlank(key) && ! RepositoryRegistry.INSTANCE.isSecretKeyValidForUser(repositoryId, username, key))
{
logger.warn("User {} tried to login with invalid secret key '{}' to repository {}", new Object[]{username, key, repositoryId});
throw new CmsLoginInvalidCredentialsException();
}
char[] password = credentials != null ? credentials.getPassword() : null;
AstroboaCredentials credentialsToSendToJAAS = new AstroboaCredentials(username,
password,
cmsRepositoryToBeConnected.getIdentityStoreRepositoryId(),
cmsRepositoryToBeConnected.getExternalIdentityStoreJNDIName(),
repositoryId,
key);
//Authenticate user using JAAS application security domain
//and create security context
int currentAuthenticationTokenTimeout = retrieveAuthenticationTokenTimeoutForRepository(repositoryId);
SecurityContext securityContext = authenticate(credentialsToSendToJAAS,repositoryId, currentAuthenticationTokenTimeout, permanentKey);
//Create a new AstroboaClientContext
RepositoryContext repositoryContext = new RepositoryContext(cmsRepositoryToBeConnected, securityContext);
AstroboaClientContext clientContext = new AstroboaClientContext(repositoryContext, lazyLoader);
AstroboaClientContextHolder.registerClientContext(clientContext, true);
//Need context to be registered first
addRepositoryUserIdPrincipalToSubject(securityContext.getSubject(), securityContext.getIdentity());
if (logger.isDebugEnabled()){
logger.debug("Sucessfully logged in to repository {} and register ClientContext {} to Thread {}", new Object[]{repositoryId, clientContext, Thread.currentThread().getName()});
}
return securityContext.getAuthenticationToken();
}
public String loginAsAnonymous(String repositoryId, String permanentKey) {
return login(repositoryId, IdentityPrincipal.ANONYMOUS, null, permanentKey);
}
public String login(String repositoryId, String username, String key,
String permanentKey) {
AstroboaCredentials credentials = new AstroboaCredentials(username);
return login(repositoryId, credentials, permanentKey, key);
}
public String loginAsAdministrator(String repositoryId, String key,
String permanentKey) {
String administratorUsername = RepositoryRegistry.INSTANCE.retrieveAdminUsernameForRepository(repositoryId);
if (StringUtils.isBlank(administratorUsername))
{
throw new CmsLoginInvalidUsernameException("No administrator username found");
}
return login(repositoryId, administratorUsername, key, permanentKey);
}
public String login(String repositoryId, Subject subject, String permanentKey) {
if (StringUtils.isBlank(repositoryId) || ! RepositoryRegistry.INSTANCE.isRepositoryRegistered(repositoryId)){
throw new CmsException("Invalid repository id "+ repositoryId);
}
//In case configuration has changed, load any changes prior to login
deployRepositoriesFoundInTheRepositoryRegistry();
if (!repositoryInfos.containsKey(repositoryId)){
throw new CmsException("Repository id '"+repositoryId + "' found in configuration but could not be loaded");
}
authorizeSubject(subject, repositoryId);
CmsRepository cmsRepositoryToBeConnected = repositoryInfos.get(repositoryId);
int currentAuthenticationTokenTimeout = retrieveAuthenticationTokenTimeoutForRepository(repositoryId);
String authenticationToken;
try {
authenticationToken = createAuthenticationToken(subject, repositoryId, permanentKey);
} catch (NoSuchAlgorithmException e) {
throw new CmsException(e);
}
SecurityContext securityContext = new SecurityContext(authenticationToken, subject, currentAuthenticationTokenTimeout,
getAvailableRepositoryIds());
//Create a new AstroboaClientContext
RepositoryContext repositoryContext = new RepositoryContext(cmsRepositoryToBeConnected, securityContext);
AstroboaClientContext clientContext = new AstroboaClientContext(repositoryContext, lazyLoader);
AstroboaClientContextHolder.registerClientContext(clientContext, true);
addRepositoryUserIdPrincipalToSubject(subject, securityContext.getIdentity());
if (logger.isDebugEnabled()){
logger.debug("Sucessfully logged in to repository {} and register ClientContext {} to Thread {}", new Object[]{repositoryId, clientContext, Thread.currentThread().getName()});
}
return securityContext.getAuthenticationToken();
}
}